#include <assert.h>
#include <stdio.h>
#include <string>
#include "Errors.h"
#include "Message.h"
#include "MultiMessageQueue.h"
#include "EventProcessorImp.h"

// maximum number of types that can be handled by a queue
#define MAX_NUM_TYPES 256

// needed to initialize references in MultiMessageQueue
EventProcessorImp DummyEventProcessorImp;
Message DummyMessage;

/////////////////////////
// Internal Message stuff

MultiMessageQueue::InternalMessage::InternalMessage(const InternalMessage& aux):
  payload(aux.payload), timestamp(aux.timestamp){}

MultiMessageQueue::InternalMessage::InternalMessage(void):
  payload(DummyMessage), timestamp(0){ }


MultiMessageQueue::InternalMessage& 
MultiMessageQueue::InternalMessage::operator=(const InternalMessage& aux) {
  payload = aux.payload;
  timestamp = aux.timestamp;
  
  return *this;
}

int MultiMessageQueue::InternalMessage::GetType(void){
   return payload.Type();
}

MultiMessageQueue::InternalMessage 
MultiMessageQueue::TypeBookkeeping::NextMessage(void){
  assert(!queue.empty());
  InternalMessage ret=queue.front();
  queue.pop_front();
  return ret;
}

bool MultiMessageQueue::TypeBookkeeping::GetTypeInfo(int curr_timestamp, TypeInfo* where){
  if (queue.empty()){
    return false;
  }
  
  InternalMessage msg=queue.front();
  where->type=type;
  where->priority=priority;
  where->age=curr_timestamp-msg.GetTimestamp();
  where->numMessages=queue.size();
  
  return true;
}

    

////////////////////////
// MultiMessageQueue stuff

MultiMessageQueue::MultiMessageQueue(bool _debug, const char *_dbgMsg) :
  debug(_debug), dbgMsg(_dbgMsg), decProcessor(DummyEventProcessorImp) {
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&condVar, NULL);
  timestamp=0;
  decFct = defaultDecisionFunction;
  numMessages = 0;
}

MultiMessageQueue::~MultiMessageQueue() {
  // deleate messages in the queue
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&condVar);
}

void MultiMessageQueue::AddMessageType(int _Type, int Priority) {
  pthread_mutex_lock(&mutex);
  map<int,TypeBookkeeping>::iterator itr = typeMap.find(_Type);
  if (itr!=typeMap.end()){
    if (_Type!=DieMessage::type) {
      WARNING("Type %d already defined\n", _Type);
    }
  } else {
    TypeBookkeeping bk(Priority,_Type);
    typeMap.insert(pair<int,TypeBookkeeping>(_Type,bk));
  }
  pthread_mutex_unlock(&mutex);
}

void MultiMessageQueue::InsertMessage(Message& _Payload) {
  Message &Payload = _Payload;

  int _Type=Payload.Type();

  pthread_mutex_lock(&mutex);

  map<int,TypeBookkeeping>::iterator itr = typeMap.find(_Type);
  if (itr!=typeMap.end()){
    // we support this type, insert it
    TypeBookkeeping& bk = (*itr).second;
    InternalMessage msg(Payload, timestamp);
    bk.InsertMessage(msg);
    
    // increment timestamp to advance time
    timestamp++;
    
    // message in
    numMessages++;

    if(debug){
      printf("MSG IN %25s:%25s, Time=%4d\n", dbgMsg, _Payload.TypeName(), timestamp);
    }

    pthread_cond_signal(&condVar); // signal the consumer that there is stuff in
    pthread_mutex_unlock(&mutex);

  } else {
    WARNING("Type %d not supported\n", _Type);
    pthread_mutex_unlock(&mutex);
  }
}

Message& MultiMessageQueue::RemoveMessage() {
  pthread_mutex_lock(&mutex);

  // do we have anything in? if not block
  while (numMessages == 0){
    pthread_cond_wait(&condVar, &mutex);
  }

  // now we know something is inside
  assert(numMessages!=0);

  // form the information for the decision function
  TypeInfo types[MAX_NUM_TYPES];
  int num = 0;

  for (map<int,TypeBookkeeping>::iterator itr = typeMap.begin();
       itr!=typeMap.end(); itr++){
    TypeBookkeeping& bk = (*itr).second;
    if (bk.GetTypeInfo(timestamp,&types[num])){
      // we have at least one message
      num++;
    }
  }

  // call the decision function to figure out what message to pop
  int typeRet = decFct(decProcessor, types, num);
  
  // get the message of type typeRet

  map<int,TypeBookkeeping>::iterator itr = typeMap.find(typeRet);
  assert(itr!=typeMap.end());
  
  TypeBookkeeping& bk = (*itr).second;
  InternalMessage msg=bk.NextMessage();
  
  // message extracted
  numMessages--;

  if (debug == true){
    printf("MSG OUT%25s:%25s, Time=%4d, QSize=%3d\n", 
					 dbgMsg, msg.GetPayload().TypeName(), msg.GetTimestamp(),
	   numMessages);
  }

  pthread_mutex_unlock(&mutex);

  return msg.GetPayload();

}


int MultiMessageQueue::GetSize() {
  return numMessages;
}


int MultiMessageQueue::defaultDecisionFunction
(EventProcessorImp& _obj, TypeInfo* arrayTypeInfo, int num){
  assert(num>=1); // we must have at least one message to be called

  // the default decision function just finds the highest priority message
  int minP=arrayTypeInfo[0].priority;
  int pos=0;
  for (int i=1; i<num; i++){
    if (arrayTypeInfo[i].priority<minP){
      minP=arrayTypeInfo[i].priority;
      pos=i;
    }
  }
  return arrayTypeInfo[pos].type;
}

